module main;

/*
 * autor: Paweł Piątkowski (pablo)
 * 
 * STEROWANIE:
 *  strzałki 			- obracanie bryłą
 *  W,S,A,D,Q,Z			- poruszanie bryłą
 *  SHIFT + strzalki 	- obracanie kamerą
 *  SHIFT + W,S,A,D,Q,Z - poruszanie kamerą
 *  L					- włączanie/wyłączanie oświetlenia
 *  T					- włączanie/wyłączanie teksturowania
 */

// TODO: fullscreen wlaczany z linii polecen
// TODO: poruszanie kamerą za pomocą myszki
// TODO: staly frame-rate
// TODO: ustaw oswietlenie

import std.stdio;
import std.string;

import derelict.opengl.gl;
import derelict.sdl.sdl;
import derelict.opengl.glu;

const int WINDOW_WIDTH = 800;
const int WINDOW_HEIGHT = 600;
const int BITS_PER_PIXEL = 24;

// view frustum
const float FOV = 90.f;
const float NEAR_PLANE = .1f;
const float FAR_PLANE = 100.f;

// textures
const char* GFX_PATH = "gfx/";
const char* GFX_TEXTURE_CHIP 	= GFX_PATH ~ "chip.bmp";
const char* GFX_TEXTURE_DANGER 	= GFX_PATH ~ "danger.bmp";
const char* GFX_TEXTURE_RED 	= GFX_PATH ~ "red.bmp";

const bool FULL_SCREEN_MODE = false;

// key codes - must have only one '1' in binary representation
const int  KEY_UP 		= 1;
const int  KEY_DOWN 	= (1 << 1);
const int  KEY_LEFT 	= (1 << 2);
const int  KEY_RIGHT 	= (1 << 3);
const int  KEY_W 		= (1 << 4);
const int  KEY_S 		= (1 << 5);
const int  KEY_A 		= (1 << 6);
const int  KEY_D 		= (1 << 7);
const int  KEY_L 		= (1 << 8);
const int  KEY_T		= (1 << 9);
const int  KEY_SHIFT	= (1 << 10);
const int  KEY_Q		= (1 << 11);
const int  KEY_Z		= (1 << 12);

const GLfloat ROTATION_STEP = 0.3f;
const GLfloat TRANSLATION_STEP = 0.005f; 

int keysState = 0;
bool lightingEnabled = true;
bool texturingEnabled = true;

GLfloat xBlockRot 	= 0.0f;
GLfloat yBlockRot 	= 0.0f;
GLfloat xBlockTr 	= 0.0f;
GLfloat yBlockTr 	= 0.0f;
GLfloat zBlockTr 	= -5.0f; 
GLfloat xCamRot		= 0.0f;
GLfloat yCamRot		= 0.0f;
GLfloat xCamTr		= 0.0f;
GLfloat yCamTr		= 0.0f;
GLfloat zCamTr		= 0.0f;
GLuint texture, texture2;
bool run = true;


void init()
{
	// initialize SDL, GL and GLU Derelict modules
	try{
		DerelictSDL.load();
		DerelictGL.load();
		DerelictGLU.load();
	} catch (Exception e) {
	    writefln("Could not load OpenGL, GLU or SDL shared library.");
	}
	
	// initialize SDL's VIDEO module
	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
	
	// enable double-buffering
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
	
	// create OpenGL window
	SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, BITS_PER_PIXEL, SDL_OPENGL | ( FULL_SCREEN_MODE ? SDL_FULLSCREEN : 0 ) );// | SDL_FULLSCREEN);
	SDL_WM_SetCaption(toStringz("OpenGL"), null);
	
	// texture
	loadTextures();
	if (texturingEnabled)
	{
		glEnable(GL_TEXTURE_2D);
	}
	
	// lighting
	if (lightingEnabled)
	{
	    glEnable(GL_LIGHTING); //Enable lighting
	    glEnable(GL_LIGHT0); //Enable light #0
//	    glEnable(GL_LIGHT1); //Enable light #1
	    glEnable(GL_NORMALIZE); //Have OpenGL automatically normalize our normals
	    glShadeModel(GL_SMOOTH); //Enable smooth shading
	    glEnable(GL_COLOR_MATERIAL);
	}
    
	// z-buffer?
	glEnable(GL_DEPTH_TEST);
	
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    
	setupGL();
}


void setupGL()
{
	// switch to the projection mode matrix
	glMatrixMode(GL_PROJECTION);

	// load the identity matrix for projection
	glLoadIdentity();
	
	// setup a perspective projection matrix
	gluPerspective(FOV, cast(float)WINDOW_WIDTH / WINDOW_HEIGHT, NEAR_PLANE, FAR_PLANE);
	
	// switch back to the modelview transformation matrix
	glMatrixMode(GL_MODELVIEW);

	// load the identity matrix for modelview
	glLoadIdentity();
}


void loadTextures()
{
	loadTexture(GFX_TEXTURE_CHIP, &texture);
	loadTexture(GFX_TEXTURE_RED, &texture2);
}

/** loads image and prepare texture */
void loadTexture(char* texturePath, GLuint *texture)
{
	SDL_Surface* image;
	image = SDL_LoadBMP(texturePath);
	
	if (image !is null)
	{
		scope(exit) SDL_FreeSurface(image);

		glGenTextures(1, texture);
		glBindTexture(GL_TEXTURE_2D, *texture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, 3, image.w, image.h, 0,
			GL_BGR, GL_UNSIGNED_BYTE, image.pixels);
	}
}


void drawGLFrame()
{
	//glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	
	// translating and rotating the camera
	glTranslatef(xCamTr, yCamTr, zCamTr/1.5f);
	glRotatef(yCamRot, 0.0f, 1.0f, 0.0f);
	glRotatef(xCamRot, 1.0f, 0.0f, 0.0f);
	
	glPushMatrix();
	glTranslatef(xBlockTr, yBlockTr, zBlockTr/1.5f);
	glRotatef(yBlockRot, 0.0f, 1.0f, 0.0f);
	glRotatef(xBlockRot, 1.0f, 0.0f, 0.0f);
	glBindTexture(GL_TEXTURE_2D, texture);
	
	glBegin(GL_QUADS);
		// front
		glNormal3f(0.0f, 0.0f, 1.0f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(-1.0f, -1.0f, 1.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(1.0f, -1.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, 1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, 1.0f, 1.0f);
	    // back
	    glNormal3f(0.0f, 0.0f, -1.0f);
	    glTexCoord2f(0.0f, 1.0f);
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, -1.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(1.0f, -1.0f, -1.0f);
		// top
		glNormal3f(0.0f, 1.0f, 0.0f);		
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(-1.0f, 1.0f, 1.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(1.0f, 1.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, -1.0f);
		// bottom
		glNormal3f(0.0f, -1.0f, 0.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(1.0f, -1.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(-1.0f, -1.0f, 1.0f);
	glEnd();
	
	glBegin(GL_TRIANGLES);
	/* pyramid right */
		// top
		glNormal3f(1.0f, 1.0f, 0.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, -1.0f);
		glTexCoord2f(0.5f, 1.0f);
		glVertex3f(2.0f, 0.0f, 0.0f);
	    // back
	    glNormal3f(1.0f, 0.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, -1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.5f, 1.0f);
		glVertex3f(2.0f, 0.0f, 0.0f);
		// bottom
		glNormal3f(1.0f, -1.0f, 0.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, -1.0f, 1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.5f, 1.0f);
		glVertex3f(2.0f, 0.0f, 0.0f);
		// front
	    glNormal3f(1.0f, 0.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, 1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(1.0f, -1.0f, 1.0f);
		glTexCoord2f(0.5f, 1.0f);
		glVertex3f(2.0f, 0.0f, 0.0f);
		
	/* pyramid left */
		// top
		glNormal3f(-1.0f, 1.0f, 0.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(-1.0f, 1.0f, 1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glTexCoord2f(0.5f, 1.0f);
		glVertex3f(-2.0f, 0.0f, 0.0f);
	    // back
		glNormal3f(-1.0f, 0.0f, -1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.5f, 1.0f);
		glVertex3f(-2.0f, 0.0f, 0.0f);
		// bottom
		glNormal3f(-1.0f, -1.0f, 0.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, -1.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.5f, 1.0f);
		glVertex3f(-2.0f, 0.0f, 0.0f);
		// front
	    glNormal3f(-1.0f, 0.0f, 1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, 1.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(-1.0f, -1.0f, 1.0f);
		glTexCoord2f(0.5f, 1.0f);
		glVertex3f(-2.0f, 0.0f, 0.0f);
	glEnd();
	
	glPopMatrix();
	
	glPushMatrix();
	
	/* small cube */
	glBindTexture(GL_TEXTURE_2D, texture2);
	glTranslatef(-3.0f, -2.0f, -5.0f);
	glBegin(GL_QUADS);
		// front
		glNormal3f(0.0f, 0.0f, 1.0f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(-1.0f, -1.0f, 1.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(1.0f, -1.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, 1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, 1.0f, 1.0f);
	    // back
	    glNormal3f(0.0f, 0.0f, -1.0f);
	    glTexCoord2f(0.0f, 1.0f);
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, -1.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(1.0f, -1.0f, -1.0f);
		// top
		glNormal3f(0.0f, 1.0f, 0.0f);		
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(-1.0f, 1.0f, 1.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(1.0f, 1.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, -1.0f);
		// bottom
		glNormal3f(0.0f, -1.0f, 0.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(1.0f, -1.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(-1.0f, -1.0f, 1.0f);
		// left
		glNormal3f(-1.0f, 0.0f, 0.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, 1.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(-1.0f, -1.0f, 1.0f);
		// right
		glNormal3f(1.0f, 0.0f, 0.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(1.0f, 1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, 1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, -1.0f, 1.0f);
	glEnd();
	glPopMatrix();
	glFlush();
	
}

/** maps SDL key events to my key events */
int keyMap(int sdlKey)
{
	int k = 0;
	switch (sdlKey)
	{
		case SDLK_UP:
			k = KEY_UP; 
			break;
		case SDLK_DOWN:
			k = KEY_DOWN;
			break;
		case SDLK_LEFT:
			k = KEY_LEFT;
			break;
		case SDLK_RIGHT:
			k = KEY_RIGHT;
			break;
		case SDLK_w:
			k = KEY_W;
			break;
		case SDLK_s:
			k = KEY_S;
			break;
		case SDLK_a:
			k = KEY_A;
			break;
		case SDLK_d:
			k = KEY_D;
			break;
		case SDLK_q:
			k = KEY_Q;
			break;
		case SDLK_z:
			k = KEY_Z;
			break;
		case SDLK_l:
			k = KEY_L;
			break;
		case SDLK_t:
			k = KEY_T;
			break;
		case SDLK_LSHIFT:
			k = KEY_SHIFT;
			break;
		case SDLK_ESCAPE:
			run = false;
			break;
		default:
			break;
				
	}
	return k;
}

/** handles user's input */
void input()()
{
	SDL_Event event;
	while(SDL_PollEvent(&event))
	{
		switch(event.type)
		{
			case SDL_KEYUP:
				keysState &= ~keyMap(event.key.keysym.sym);
				break;
			case SDL_QUIT:
				run = false;
				break;
			case SDL_KEYDOWN:
				keysState |= keyMap(event.key.keysym.sym);
				break;
			default:
				break;
		}
	}
	
	if ( (keysState & KEY_UP) != 0)
		if ( (keysState & KEY_SHIFT) != 0)
			xCamRot -= ROTATION_STEP;
		else
			xBlockRot -= ROTATION_STEP;
	if ( (keysState & KEY_DOWN) != 0)
		if ( (keysState & KEY_SHIFT) != 0)
			xCamRot += ROTATION_STEP;
		else
			xBlockRot += ROTATION_STEP;
	if ( (keysState & KEY_RIGHT) != 0)
		if ( (keysState & KEY_SHIFT) != 0)
			yCamRot += ROTATION_STEP;
		else
			yBlockRot += ROTATION_STEP;
	if ( (keysState & KEY_LEFT) != 0)
		if ( (keysState & KEY_SHIFT) != 0)
			yCamRot -= ROTATION_STEP;
		else
			yBlockRot -= ROTATION_STEP;
	if ( (keysState & KEY_A) != 0)
		if ( (keysState & KEY_SHIFT) != 0)
			xCamTr += TRANSLATION_STEP;
		else
			xBlockTr -= TRANSLATION_STEP;
	if ( (keysState & KEY_D) != 0)
		if ( (keysState & KEY_SHIFT) != 0)
			xCamTr -= TRANSLATION_STEP;
		else
			xBlockTr += TRANSLATION_STEP;
	if ( (keysState & KEY_S) != 0)
		if ( (keysState & KEY_SHIFT) != 0)
			yCamTr += TRANSLATION_STEP;
		else
			yBlockTr -= TRANSLATION_STEP;
	if ( (keysState & KEY_W) != 0)
		if ( (keysState & KEY_SHIFT) != 0)
			yCamTr -= TRANSLATION_STEP;
		else
			yBlockTr += TRANSLATION_STEP;
	if ( (keysState & KEY_Q) != 0)
		if ( (keysState & KEY_SHIFT) != 0)
			zCamTr += TRANSLATION_STEP;
		else
			zBlockTr -= TRANSLATION_STEP;
	if ( (keysState & KEY_Z) != 0)
		if ( (keysState & KEY_SHIFT) != 0)
			zCamTr -= TRANSLATION_STEP;
		else
			zBlockTr += TRANSLATION_STEP;
	if ( (keysState & KEY_L) != 0)
	{
		switchLighting();
		keysState &= ~KEY_L; // to avoid effect of multiple keypress
	}
	if ( (keysState & KEY_T) != 0)
	{
		switchTexturing();
		keysState &= ~KEY_T; // to avoid effect of multiple keypress
	}
} 

/** release all resources */
void cleanup()
{
	SDL_Quit();
	
	DerelictGLU.unload();
	DerelictGL.unload();
	DerelictSDL.unload();
}

/** enables/disables lighting */
void switchLighting()
{
	lightingEnabled ? glDisable(GL_LIGHTING) : glEnable(GL_LIGHTING);
	lightingEnabled = !lightingEnabled;
}

/** enables/disables texturing  */
void switchTexturing()
{
	texturingEnabled ? glDisable(GL_TEXTURE_2D) : glEnable(GL_TEXTURE_2D);
	texturingEnabled = !texturingEnabled;
}

/******************** MAIN ***********************/

void main()
{
	init();
	
	// cleanup() on exit
	scope(exit) cleanup();
	
	
	// MAIN LOOP
	while(run)
	{
		// interaction with user
		input();	
		
        // clearing the screen
        glClear(GL_COLOR_BUFFER_BIT);

        // drawing
        drawGLFrame();

        // swap buffers
        SDL_GL_SwapBuffers();
	}
}